using AssetRipper.Export.UnityProjects.Textures;
using AssetRipper.Import.Logging;
using AssetRipper.SourceGenerated.Classes.ClassID_117;
using AssetRipper.SourceGenerated.Classes.ClassID_187;
using AssetRipper.SourceGenerated.Classes.ClassID_188;
using AssetRipper.SourceGenerated.Classes.ClassID_189;
using AssetRipper.SourceGenerated.Classes.ClassID_28;
using AssetRipper.SourceGenerated.Classes.ClassID_89;
using AssetRipper.SourceGenerated.Enums;
using AssetRipper.SourceGenerated.Extensions;
using AssetRipper.TextureDecoder.Astc;
using AssetRipper.TextureDecoder.Atc;
using AssetRipper.TextureDecoder.Bc;
using AssetRipper.TextureDecoder.Dxt;
using AssetRipper.TextureDecoder.Etc;
using AssetRipper.TextureDecoder.Pvrtc;
using AssetRipper.TextureDecoder.Rgb;
using AssetRipper.TextureDecoder.Rgb.Formats;
using AssetRipper.TextureDecoder.Yuy2;
using System.Runtime.CompilerServices;

namespace AssetRipper.Export.Modules.Textures;

public static class TextureConverter
{
	public static bool TryConvertToBitmap(IImageTexture texture, out DirectBitmap bitmap)
	{
		return texture switch
		{
			ICubemapArray cubemapArray => TryConvertToBitmap(cubemapArray, out bitmap),
			ITexture2DArray texture2DArray => TryConvertToBitmap(texture2DArray, out bitmap),
			ITexture3D texture3D => TryConvertToBitmap(texture3D, out bitmap),
			ITexture2D texture2D => TryConvertToBitmap(texture2D, out bitmap),
			_ => ReturnFalse(out bitmap),
		};

		static bool ReturnFalse(out DirectBitmap bitmap)
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}
	}

	public static bool TryConvertToBitmap(ITexture3D texture, out DirectBitmap bitmap)
	{
		byte[] buffer = texture.GetImageData();
		if (buffer.Length == 0)
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryGetTextureFormat((GraphicsFormat)texture.Format, out TextureFormat format))
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryConvertToBitmap(
			format,
			texture.Width,
			texture.Height,
			texture.Depth,
			texture.GetCompleteImageSize(),
			texture.Collection.Version,
			buffer,
			out bitmap))
		{
			return false;
		}

		bitmap.FlipY();

		// despite the name, this packing works for different formats
		if (texture.LightmapFormatE == TextureUsageMode.NormalmapDXT5nm)
		{
			UnpackNormal(bitmap.Bits);
		}

		return true;
	}

	public static bool TryConvertToBitmap(ITexture2DArray texture, out DirectBitmap bitmap)
	{
		byte[] buffer = texture.GetImageData();
		if (buffer.Length == 0)
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryGetTextureFormat((GraphicsFormat)texture.Format, out TextureFormat format))
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryConvertToBitmap(
			format,
			texture.Width,
			texture.Height,
			texture.Depth,
			texture.GetCompleteImageSize(),
			texture.Collection.Version,
			buffer,
			out bitmap))
		{
			return false;
		}

		bitmap.FlipY();

		return true;
	}

	public static bool TryConvertToBitmap(ICubemapArray texture, out DirectBitmap bitmap)
	{
		byte[] buffer = texture.GetImageData();
		if (buffer.Length == 0)
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryGetTextureFormat((GraphicsFormat)texture.Format, out TextureFormat format))
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryConvertToBitmap(
			format,
			texture.Width,
			texture.GetHeight(),
			texture.GetDepth(),
			texture.GetCompleteImageSize(),
			texture.Collection.Version,
			buffer,
			out bitmap))
		{
			return false;
		}

		bitmap.FlipY();// Maybe not needed?

		return true;
	}

	public static bool TryConvertToBitmap(ITexture2D texture, out DirectBitmap bitmap)
	{
		byte[] buffer = texture.GetImageData();
		if (buffer.Length == 0)
		{
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (!TryConvertToBitmap(
			texture.Format_C28E,
			texture.Width_C28,
			texture.Height_C28,
			texture.ImageCount_C28,
			texture.ActualImageSize,
			texture.Collection.Version,
			buffer,
			out bitmap))
		{
			return false;
		}

		// cubemaps dont need flipping, for some reason
		if (texture is not ICubemap)
		{
			bitmap.FlipY();
		}

		// despite the name, this packing works for different formats
		if (texture.LightmapFormat_C28E == TextureUsageMode.NormalmapDXT5nm)
		{
			UnpackNormal(bitmap.Bits);
		}

		return true;
	}

	private static bool TryConvertToBitmap(
		TextureFormat textureFormat,
		int width,
		int height,
		int depth,
		int imageSize,
		UnityVersion version,
		byte[] data,
		out DirectBitmap bitmap)
	{
		return textureFormat switch
		{
			TextureFormat.Alpha8 => TryConvertToBitmap<ColorA<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.ARGB4444 => TryConvertToBitmap<ColorARGB16, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB24 => TryConvertToBitmap<ColorRGB<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBA32 => TryConvertToBitmap<ColorRGBA<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.ARGB32 => TryConvertToBitmap<ColorARGB<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.ARGBFloat => TryConvertToBitmap<ColorARGB<float>, float>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB565 => TryConvertToBitmap<ColorRGB16, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.BGR24 => TryConvertToBitmap<ColorBGR<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.R16 => TryConvertToBitmap<ColorR<ushort>, ushort>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBA4444 => TryConvertToBitmap<ColorRGBA16, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.BGRA32_14 or TextureFormat.BGRA32_37 => TryConvertToBitmap<ColorBGRA<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RHalf => TryConvertToBitmap<ColorR<Half>, Half>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGHalf => TryConvertToBitmap<ColorRG<Half>, Half>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBAHalf => TryConvertToBitmap<ColorRGBA<Half>, Half>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RFloat => TryConvertToBitmap<ColorR<float>, float>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGFloat => TryConvertToBitmap<ColorRG<float>, float>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBAFloat => TryConvertToBitmap<ColorRGBA<float>, float>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB9e5Float => TryConvertToBitmap<ColorRGB9e5, double>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RG16 => TryConvertToBitmap<ColorRG<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.R8 => TryConvertToBitmap<ColorR<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RG32 => TryConvertToBitmap<ColorRG<ushort>, ushort>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB48 => TryConvertToBitmap<ColorRGB<ushort>, ushort>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBA64 => TryConvertToBitmap<ColorRGBA<ushort>, ushort>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.R8_SIGNED => TryConvertToBitmap<ColorR<sbyte>, sbyte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RG16_SIGNED => TryConvertToBitmap<ColorRG<sbyte>, sbyte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB24_SIGNED => TryConvertToBitmap<ColorRGB<sbyte>, sbyte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBA32_SIGNED => TryConvertToBitmap<ColorRGBA<sbyte>, sbyte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.R16_SIGNED => TryConvertToBitmap<ColorR<short>, short>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RG32_SIGNED => TryConvertToBitmap<ColorRG<short>, short>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGB48_SIGNED => TryConvertToBitmap<ColorRGB<short>, short>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			TextureFormat.RGBA64_SIGNED => TryConvertToBitmap<ColorRGBA<short>, short>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
			_ => TryConvertToBitmap<ColorRGBA<byte>, byte>(textureFormat, width, height, depth, imageSize, version, data, out bitmap),
		};
	}

	private static bool TryConvertToBitmap<TColor, TChannelValue>(
		TextureFormat textureFormat,
		int width,
		int height,
		int depth,
		int imageSize,
		UnityVersion version,
		byte[] data,
		out DirectBitmap bitmap)
		where TColor : unmanaged, IColor<TChannelValue>
		where TChannelValue : unmanaged
	{
		if (width <= 0 || height <= 0 || depth <= 0)
		{
			Logger.Log(LogType.Error, LogCategory.Export, $"Invalid texture dimensions. Width: {width}, Height: {height}, Depth: {depth}.");
			bitmap = DirectBitmap.Empty;
			return false;
		}

		if (1L * width * height * depth * Unsafe.SizeOf<TColor>() > int.MaxValue)
		{
			Logger.Log(LogType.Error, LogCategory.Export, $"Texture size is too large. Width: {width}, Height: {height}, Depth: {depth}.");
			bitmap = DirectBitmap.Empty;
			return false;
		}

		ReadOnlySpan<byte> uncompressedSpan;
		int bytesPerLayer;
		if (textureFormat.IsCrunched())
		{
			if (CrunchHandler.DecompressCrunch(textureFormat, version, data, out byte[]? decompressedData))
			{
				uncompressedSpan = decompressedData;
				bytesPerLayer = decompressedData.Length / depth;
			}
			else
			{
				bitmap = DirectBitmap.Empty;
				return false;
			}
		}
		else
		{
			if (data.Length == imageSize)
			{
				// This can happen for Texture3D
				// For mips, all 3 dimensions get halved with each mip level, unlike Texture2DArray.
				// https://github.com/AssetRipper/AssetRipper/issues/1886
				bytesPerLayer = -1;
			}
			else if (data.Length < (long)imageSize * depth)
			{
				Logger.Log(LogType.Error, LogCategory.Export, $"Image data length {data.Length} is less than expected {(long)imageSize * depth}. Width: {width}, Height: {height}, Depth: {depth}, Image Size: {imageSize}, Format {textureFormat}.");
				bitmap = DirectBitmap.Empty;
				return false;
			}
			else
			{
				bytesPerLayer = imageSize;
			}
			uncompressedSpan = data;
		}

		bitmap = new DirectBitmap<TColor, TChannelValue>(width, height, depth);
		int outputSize = width * height * bitmap.PixelSize;
		int inputOffset = 0;
		for (int i = 0; i < depth; i++)
		{
			ReadOnlySpan<byte> inputSpan = uncompressedSpan.Slice(inputOffset, int.Max(uncompressedSpan.Length - inputOffset, bytesPerLayer));
			Span<byte> outputSpan = bitmap.Bits.Slice(i * outputSize, outputSize);

			int bytesRead = DecodeTexture<TColor, TChannelValue>(textureFormat, width, height, inputSpan, outputSpan);
			if (bytesRead < 0)
			{
				bitmap = DirectBitmap.Empty;
				return false;
			}

			inputOffset += bytesPerLayer > 0 ? bytesPerLayer : bytesRead;
		}
		return true;
	}

	private static int DecodeTexture<TColor, TChannelValue>(TextureFormat textureFormat, int width, int height, ReadOnlySpan<byte> inputSpan, Span<byte> outputSpan)
		where TColor : unmanaged, IColor<TChannelValue>
		where TChannelValue : unmanaged
	{
		switch (textureFormat)
		{
			//ASTC
			case TextureFormat.ASTC_RGB_4x4:
			case TextureFormat.ASTC_RGBA_4x4:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 4, 4, outputSpan);

			case TextureFormat.ASTC_RGB_5x5:
			case TextureFormat.ASTC_RGBA_5x5:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 5, 5, outputSpan);

			case TextureFormat.ASTC_RGB_6x6:
			case TextureFormat.ASTC_RGBA_6x6:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 6, 6, outputSpan);

			case TextureFormat.ASTC_RGB_8x8:
			case TextureFormat.ASTC_RGBA_8x8:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 8, 8, outputSpan);

			case TextureFormat.ASTC_RGB_10x10:
			case TextureFormat.ASTC_RGBA_10x10:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 10, 10, outputSpan);

			case TextureFormat.ASTC_RGB_12x12:
			case TextureFormat.ASTC_RGBA_12x12:
				return AstcDecoder.DecodeASTC<TColor, TChannelValue>(inputSpan, width, height, 12, 12, outputSpan);

			//ATC
			case TextureFormat.ATC_RGB4:
				return AtcDecoder.DecompressAtcRgb4<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ATC_RGBA8:
				return AtcDecoder.DecompressAtcRgba8<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			//BC
			case TextureFormat.BC4:
				return Bc4.Decompress<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.BC5:
				return Bc5.Decompress<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.BC6H:
				return Bc6h.Decompress<TColor, TChannelValue>(inputSpan, width, height, false, outputSpan);

			case TextureFormat.BC7:
				return Bc7.Decompress<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			//DXT
			case TextureFormat.DXT1:
			case TextureFormat.DXT1Crunched:
				return DxtDecoder.DecompressDXT1<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.DXT3:
				return DxtDecoder.DecompressDXT3<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.DXT5:
			case TextureFormat.DXT5Crunched:
				return DxtDecoder.DecompressDXT5<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			//ETC
			case TextureFormat.ETC_RGB4:
			case TextureFormat.ETC_RGB4_3DS:
			case TextureFormat.ETC_RGB4Crunched:
				return EtcDecoder.DecompressETC<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.EAC_R:
				return EtcDecoder.DecompressEACRUnsigned<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.EAC_R_SIGNED:
				return EtcDecoder.DecompressEACRSigned<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.EAC_RG:
				return EtcDecoder.DecompressEACRGUnsigned<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.EAC_RG_SIGNED:
				return EtcDecoder.DecompressEACRGSigned<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ETC2_RGB:
				return EtcDecoder.DecompressETC2<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ETC2_RGBA1:
				return EtcDecoder.DecompressETC2A1<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ETC2_RGBA8:
			case TextureFormat.ETC_RGBA8_3DS:
			case TextureFormat.ETC2_RGBA8Crunched:
				return EtcDecoder.DecompressETC2A8<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			//PVRTC
			case TextureFormat.PVRTC_RGB2:
			case TextureFormat.PVRTC_RGBA2:
				return PvrtcDecoder.DecompressPVRTC<TColor, TChannelValue>(inputSpan, width, height, true, outputSpan);

			case TextureFormat.PVRTC_RGB4:
			case TextureFormat.PVRTC_RGBA4:
				return PvrtcDecoder.DecompressPVRTC<TColor, TChannelValue>(inputSpan, width, height, false, outputSpan);

			//YUY2
			case TextureFormat.YUY2:
				return Yuy2Decoder.DecompressYUY2<TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			//RGB
			case TextureFormat.Alpha8:
				return RgbConverter.Convert<ColorA<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ARGB4444:
				return RgbConverter.Convert<ColorARGB16, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBA4444:
				return RgbConverter.Convert<ColorRGBA16, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB565:
				return RgbConverter.Convert<ColorRGB16, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.R8:
				return RgbConverter.Convert<ColorR<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RG16:
				return RgbConverter.Convert<ColorRG<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB24:
				return RgbConverter.Convert<ColorRGB<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBA32:
				return RgbConverter.Convert<ColorRGBA<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ARGB32:
				return RgbConverter.Convert<ColorARGB<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.ARGBFloat:
				return RgbConverter.Convert<ColorARGB<float>, float, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.BGR24:
				return RgbConverter.Convert<ColorBGR<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.BGRA32_14:
			case TextureFormat.BGRA32_37:
				return RgbConverter.Convert<ColorBGRA<byte>, byte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.R16:
				return RgbConverter.Convert<ColorR<ushort>, ushort, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RG32:
				return RgbConverter.Convert<ColorRG<ushort>, ushort, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB48:
				return RgbConverter.Convert<ColorRGB<ushort>, ushort, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBA64:
				return RgbConverter.Convert<ColorRGBA<ushort>, ushort, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RHalf:
				return RgbConverter.Convert<ColorR<Half>, Half, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGHalf:
				return RgbConverter.Convert<ColorRG<Half>, Half, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBAHalf:
				return RgbConverter.Convert<ColorRGBA<Half>, Half, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RFloat:
				return RgbConverter.Convert<ColorR<float>, float, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGFloat:
				return RgbConverter.Convert<ColorRG<float>, float, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBAFloat:
				return RgbConverter.Convert<ColorRGBA<float>, float, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB9e5Float:
				return RgbConverter.Convert<ColorRGB9e5, double, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.R8_SIGNED:
				return RgbConverter.Convert<ColorR<sbyte>, sbyte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RG16_SIGNED:
				return RgbConverter.Convert<ColorRG<sbyte>, sbyte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB24_SIGNED:
				return RgbConverter.Convert<ColorRGB<sbyte>, sbyte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBA32_SIGNED:
				return RgbConverter.Convert<ColorRGBA<sbyte>, sbyte, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.R16_SIGNED:
				return RgbConverter.Convert<ColorR<short>, short, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RG32_SIGNED:
				return RgbConverter.Convert<ColorRG<short>, short, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGB48_SIGNED:
				return RgbConverter.Convert<ColorRGB<short>, short, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			case TextureFormat.RGBA64_SIGNED:
				return RgbConverter.Convert<ColorRGBA<short>, short, TColor, TChannelValue>(inputSpan, width, height, outputSpan);

			default:
				Logger.Log(LogType.Error, LogCategory.Export, $"Unsupported texture format '{textureFormat}'");
				return -1;
		}
	}

	private static bool TryGetTextureFormat(GraphicsFormat graphicsFormat, out TextureFormat format)
	{
		try
		{
			format = graphicsFormat.ToTextureFormat();
			return true;
		}
		catch (NotSupportedException)
		{
			format = default;
			return false;
		}
		catch (ArgumentOutOfRangeException)
		{
			Logger.Log(LogType.Error, LogCategory.Export, $"Unknown GraphicsFormat '{(int)graphicsFormat}'");
			format = default;
			return false;
		}
	}

	private static void UnpackNormal(Span<byte> data)
	{
		for (int i = 0; i < data.Length; i += 4)
		{
			Span<byte> pixelSpan = data.Slice(i, 4);
			byte r = pixelSpan[3];
			byte g = pixelSpan[1];
			byte a = pixelSpan[2];
			pixelSpan[2] = r;
			pixelSpan[3] = a;

			const double MagnitudeSqr = 255 * 255;
			double vr = r * 2.0 - 255.0;
			double vg = g * 2.0 - 255.0;
			double hypotenuseSqr = Math.Min(vr * vr + vg * vg, MagnitudeSqr);
			double b = (Math.Sqrt(MagnitudeSqr - hypotenuseSqr) + 255.0) / 2.0;
			pixelSpan[0] = (byte)b;
		}
	}
}
